
#include <assert.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <stddef.h>

#include "raise.h"
#include "new.h"

typedef struct RefNode
{
    struct RefNode* prev;
    struct RefNode* next;
    Object* obj;
    size_t count;
} RefNode;

static RefNode* refs = NULL;

static RefNode* new_ref(Object* obj)
{
    extern RefNode* refs;
    RefNode* n;

    if ((n = malloc(sizeof(RefNode))) == NULL)
        raise("Out of Memory");
    n->next = refs;
    n->prev = NULL;
    if (refs != NULL)
        refs->prev = n;
    n->obj = obj;
    n->count = 0;
    refs = n;
    return (n);
}

static void remove_ref(RefNode* ref)
{
    assert(ref != NULL);
    assert(ref->count == 0);
    if (ref->prev != NULL)
        ref->prev->next = ref->next;
    else
        refs = ref->next;
    if (ref->next != NULL)
        ref->next->prev = ref->prev;
    free(ref);
}


static void init_instance(Object* instance, MetaClass const* metaclass)
{
    //printf("Init instance for %s at %p\n", metaclass->__name__, instance);
    if (get_parent_type(metaclass) != NULL)
    {
        init_instance(
            (Object*) (((char*) instance) + ((ptrdiff_t) metaclass->__size__)),
            get_parent_type(metaclass)
        );
    }
    assert(metaclass->__class__ != NULL);
    memcpy(instance, *metaclass->__class__, metaclass->__size__);
}


#define GET_REF(obj) (*(RefNode**) (((char*) obj) - sizeof(RefNode**)))

Object* va_new(Class const* cls, va_list* args)
{
    assert(cls != NULL && "Class pointer is NULL");
    size_t tot = 0;
    MetaClass const* type;
    for (type = typeof(cls); type != NULL; type = get_parent_type(type))
        tot += type->__size__;
    assert(tot > 0);

    char* data = calloc(1, tot + sizeof(RefNode**));
    if (data == 0)
        raise("Out of memory");
    Object* obj = (Object*)(data + sizeof(RefNode**));
    RefNode** ref = (RefNode**) data;
    *ref = new_ref(obj);

    init_instance(obj, typeof(cls));

    type = typeof(cls);
    do {
        if (type->__init__ != NULL)
            type->__init__((Object*) up_cast(obj, type), args);
        type = get_parent_type(type);
    } while (type != NULL);
    return obj;
}

Object* new(Class const* cls, ...)
{
    va_list ap;
    va_start(ap, cls);
    Object* obj = va_new(cls, &ap);
    va_end(ap);
    return obj;
}

Object* incref(Object* obj)
{
    GET_REF(obj)->count += 1;
    return obj;
}

void decref(Object* obj)
{
    RefNode* ref = GET_REF(obj);
    assert(ref->count > 0 && "Too many call to decref");
    ref->count -= 1;
    if (ref->count == 0)
        delete(obj);
}

void delete(Object* obj)
{
    size_t ref = GET_REF(obj)->count;
    assert(ref == 0 && "Try to delete a referenced pointer");
    MetaClass const* tmp = typeof(obj);
    //printf("Dtor of %s\n", tmp->__name__);
    while (tmp)
    {
        if (tmp->__del__)
        {
            tmp->__del__((Object*) up_cast(obj, tmp));
        }
        tmp = get_parent_type(tmp);
    }
    remove_ref(GET_REF(obj));
    free(((char*) obj) - sizeof(RefNode**));
}

void __delete_all(int dummy, ...)
{
    va_list ap;
    va_start(ap, dummy);

    Object* inst;
    while ((inst = va_arg(ap, Object*)) != NULL)
        delete(inst);
    va_end(ap);
}

void clean_all()
{
    RefNode* n = refs, *next;
    while (n != NULL)
    {
        if (n->count == 0)
        {
            next = n->next;
            delete(n->obj);
            n = next;
        }
        else
            n = n->next;
    }
}
